﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MugenInjection.Delegates;
using MugenInjection.Exceptions;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;

// ReSharper disable CheckNamespace

namespace MugenInjection.Core.Components
// ReSharper restore CheckNamespace
{
    /// <summary>
    ///     Represents the interface that can convert types when binding activating, this converter not support cache it used for dynamic converters, like adapters or decorators.
    /// </summary>
    public class DynamicConverterProvider : ConverterBase
    {
        #region Fields

        private readonly CacheCollection<Type, List<IDynamicConverter>> _typesToConverters;
        private readonly List<IDynamicConverter> _converters;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="DynamicConverterProvider" /> class.
        /// </summary>
        public DynamicConverterProvider()
            : base(1, ServiceType.Simple | ServiceType.Activatable)
        {
            _typesToConverters = new CacheCollection<Type, List<IDynamicConverter>>();
            _converters = new List<IDynamicConverter>();
            DynamicConverters = new ReadOnlyCollection<IDynamicConverter>(_converters);
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the list of dynamic converters.
        /// </summary>
        public IList<IDynamicConverter> DynamicConverters { get; private set; }

        #endregion

        #region Methods

        /// <summary>
        /// Adds the specified <see cref="IDynamicConverter"/>.
        /// </summary>
        /// <param name="converter">The specified <see cref="IDynamicConverter"/>.</param>
        public virtual void AddConverter(IDynamicConverter converter)
        {
            Validate.ArgumentNotNull(converter, "converter");
            lock (_converters)
            {
                if (_converters.Contains(converter))
                    throw new ArgumentException("The converter is already in the collection.");
                _converters.Add(converter);
                foreach (var service in converter.Services)
                {
                    OnSupportedTypeChanged(TypeChangedAction.Add, service);
                    var converters = _typesToConverters.GetOrAdd(service, () => new List<IDynamicConverter>());
                    if (!converters.Contains(converter))
                        converters.Add(converter);
                }
            }
        }

        /// <summary>
        /// Removes the specified <see cref="IDynamicConverter"/>.
        /// </summary>
        /// <param name="converter">The specified <see cref="IDynamicConverter"/>.</param>
        public virtual bool RemoveConverter(IDynamicConverter converter)
        {
            Validate.ArgumentNotNull(converter, "converter");
            lock (_converters)
            {
                if (!_converters.Contains(converter))
                    return false;
                _converters.Remove(converter);
                foreach (var service in converter.Services)
                {
                    OnSupportedTypeChanged(TypeChangedAction.Remove, service);
                    var converters = _typesToConverters[service];
                    if (converters != null)
                        converters.Remove(converter);
                }
                return true;
            }
        }

        /// <summary>
        /// Gets all converters with the specified type.
        /// </summary>
        public virtual IList<T> GetAll<T>(Predicate<T> predicate) where T : class
        {
            lock (_converters)
            {
                var list = new List<T>();
                foreach (var dynamicConverter in _converters)
                {
                    var obj = dynamicConverter as T;
                    if (obj == null || !predicate(obj)) continue;
                    list.Add(obj);
                }
                return list;
            }
        }

        /// <summary>
        /// Searches for a <see cref="IDynamicConverter"/> satisfying the specified a <see cref="IBindingContext"/>
        /// </summary>
        /// <param name="bindingContext">The specified <see cref="IBindingContext"/> for search.</param>
        /// <param name="convertType">The type to convert.</param>
        /// <returns>An instance of <see cref="IDynamicConverter"/>.</returns>
        protected virtual IDynamicConverter FindConverter(IBindingContext bindingContext, out Type convertType)
        {
            IDynamicConverter converter = null;
            convertType = null;
            lock (_converters)
            {
                var converters = _typesToConverters[bindingContext.Service];
                if (converters == null || converters.Count == 0)
                    return null;
                foreach (var dynamicConverter in converters)
                {
                    if (!dynamicConverter.CanResolve(bindingContext, out convertType)) continue;
                    if (converter != null)
                        throw new ConflictBindingException(bindingContext, true);
                    converter = dynamicConverter;
                }
            }
            return converter;
        }

        private bool IsHaveConverterWithType(Type service)
        {
            lock (_converters)
            {
                var converters = _typesToConverters[service];
                return converters != null && converters.Count != 0;
            }
        }

        private static object Convert(object instance, IDynamicConverter converter)
        {
            var converterContext = InjectorUtilsInternal.GetConverterContext(instance);
            return converter.Resolve(converterContext);
        }

        #endregion

        #region Overrides of ConverterBase

        /// <summary>
        ///     Gets the supported types.
        /// </summary>
        public override IEnumerable<Type> SupportedTypes
        {
            get { return InjectorUtilsInternal.SelectMany(DynamicConverters, converter => converter.Services); }
        }

        /// <summary>
        ///     Tries to convert instance.
        /// </summary>
        /// <param name="bindingContext">The specified type to convert from.</param>
        /// <param name="originalType">The type to converted to.</param>
        /// <param name="convertAction">An delegate to convert instance.</param>
        /// <returns>
        ///     If <c>true</c> can be converted; otherwise <c>false</c>.
        /// </returns>
        protected override ConverterResult TryConvertInternal(IBindingContext bindingContext, out Type originalType,
                                                              out Func<object, object> convertAction)
        {
            convertAction = null;
            if (!IsHaveConverterWithType(bindingContext.Service))
            {
                originalType = null;
                return ConverterResult.NotSupported;
            }
            IDynamicConverter dynamicConverter = FindConverter(bindingContext, out originalType);
            if (dynamicConverter == null)
            {
                SetNoCacheKey(bindingContext);
                return ConverterResult.NotSupported;
            }
            convertAction = o => Convert(o, dynamicConverter);
            return ConverterResult.SupportedWithoutCache;
        }

        #endregion
    }
}